package com.objectdraw.server;

import java.net.Socket;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

import com.objectdraw.server.commands.ColorChangeCommand;
import com.objectdraw.server.commands.Command;
import com.objectdraw.server.commands.ConnectCommand;
import com.objectdraw.server.commands.ControlCommand;
import com.objectdraw.server.commands.DeleteCommand;
import com.objectdraw.server.commands.DisconnectCommand;
import com.objectdraw.server.commands.FullUpdateCommand;
import com.objectdraw.server.commands.KeyboardCommand;
import com.objectdraw.server.commands.MouseCommand;
import com.objectdraw.server.commands.SaveRequestCommand;
import com.objectdraw.server.commands.ToolChangeCommand;

import com.objectdraw.server.CommunicationServer;
import com.objectdraw.server.data.Action;
import com.objectdraw.server.data.ResponseData;
import com.objectdraw.server.tools.ServerTool;
import com.objectdraw.server.tools.ServerToolFactory;
import com.objectdraw.server.tools.data.DrawingObject;

/**
 * Used mainly for storage. Contains all drawn objects, list of all users, all open sockets
 * Also contains methods for setting client side control.
 * 
 * @author jgulik, c fruehan, Harrison
 *
 */
public class ObjectDrawServer {

	private static ObjectDrawServer instance = null;
	private HashMap<String, Socket> clients = new HashMap<String, Socket>();
	private List<String> drawerQueue = new ArrayList<String>();
	private String currentDrawer = "";
	private Date currentDrawerLastActive = null;
	private Date currentDrawerStartTime = null;
	private static long idletime = 30000; // 30 seconds
	private static long timelimit = 60000 * 5; // 5 minutes
	
	//Default the current tool to freehand
	private ServerTool tool = ServerToolFactory.createTool("freehand");
	
	//Objects on canvas
	private List<DrawingObject> canvasObjects = new ArrayList<DrawingObject>();
	private boolean objectSelected = false;
	private String currentColor = "black"; //Default is black
	private List<String> viewingClientsList= new ArrayList<String>();;
	
	private ObjectDrawServer() {
		(new Thread(new ObjectDrawServerMonitor())).start();
	}

	/**
	 * Use this method to get an instance of the ObjectDraw server
	 * 
	 * @return
	 */
	public synchronized static ObjectDrawServer getInstance() {
		if (instance == null) {
			instance = new ObjectDrawServer();
		}
		return instance;
	}

	/**
	 * Returns an instance of command that can be used to process requests from
	 * the client
	 * 
	 * @return
	 */
	public static Command getCommandHandlers() {
		// TODO: Is there a better way to load the Commands??
		ConnectCommand connect = new ConnectCommand();
		DisconnectCommand disconnect = new DisconnectCommand();
		ColorChangeCommand color = new ColorChangeCommand();
		ControlCommand control = new ControlCommand();
		FullUpdateCommand update = new FullUpdateCommand();
		KeyboardCommand keyboard = new KeyboardCommand();
		MouseCommand mouse = new MouseCommand();
		SaveRequestCommand save = new SaveRequestCommand();
		ToolChangeCommand tool = new ToolChangeCommand();
		DeleteCommand delete = new DeleteCommand();

		connect.setSuccessor(disconnect);
		disconnect.setSuccessor(color);
		color.setSuccessor(control);
		control.setSuccessor(update);
		update.setSuccessor(keyboard);
		keyboard.setSuccessor(mouse);
		mouse.setSuccessor(save);
		save.setSuccessor(tool);
		tool.setSuccessor(delete);
		return connect;
	}

	/**
	 * Add a client to the object draw server (add user to session)
	 * 
	 * @param client
	 *            socket connection to the
	 * @param username
	 */
	public synchronized void addClient(Socket client, String username) {
		clients.put(username, client);
		addToViewingClients(username);

	}

	/**
	 * Remove a client from the object draw server (remove use from session)
	 * 
	 * @param uname
	 * @return the socket object used to communicate with the client
	 */
	public synchronized Socket removeClient(String uname) {
		removeFromViewingClients(uname);
		if(clients.containsKey(uname)){
			
			System.out.println("Found " +uname + " on server");
		}else{
			System.out.println("Did NOT Find " +uname + " on server");
		}
		return clients.remove(uname);
	}

	/**
	 * Add a user to the line for control of the canvas
	 * 
	 * @param user
	 */
	public synchronized void queueDrawer(String user) {

		if (!drawerQueue.contains(user) && clients.containsKey(user)) {
			drawerQueue.add(user);
		}

		if (getCurrentDrawer() == "") {
			setCurrentDrawer(user);
		}
	}

	/**
	 * Remove a user from the line waiting for control of the canvas
	 * 
	 * @param user
	 */
	public synchronized void dequeueDrawer(String user) {
		if (drawerQueue.contains(user)) {
			drawerQueue.remove(user);
		}
	}

	/**
	 * Returns the next user in line waiting for control of the canvas
	 * 
	 * @return an empty string if and only if the queue is empty
	 */
	public synchronized String getNextUserQueued() {
		if (!drawerQueue.isEmpty()) {
			return drawerQueue.get(0);
		}
		return "";
	}

	/**
	 * Allow this user to control the canvas
	 * 
	 * @param user
	 */
	public synchronized void setCurrentDrawer(String user) {
		if (drawerQueue.contains(user)) {
			ResponseData response = new ResponseData();
			response.addResponse("Control canvas denied");
			CommunicationServer.broadcast(response, getDrawerConnection(getCurrentDrawer()));
			
			
			dequeueDrawer(user);
			currentDrawer = user;
			currentDrawerLastActive = new Date();
			currentDrawerStartTime = new Date();
			System.out.println("New Drawer: " + user);
			
			ResponseData response2 = new ResponseData();
			response2.addResponse("Control canvas");
			CommunicationServer.broadcast(response2, getDrawerConnection(user));
			
			removeFromViewingClients(user);
		}
	}

	/**
	 * Releases control from the active drawer
	 */
	public synchronized void releaseControl() {
			addToViewingClients(currentDrawer);
			currentDrawer = "";
			currentDrawerLastActive = null;
			currentDrawerStartTime = null;
	}

	/**
	 * Returns the current user allowed to draw on the canvas
	 * 
	 * @return
	 */
	public synchronized String getCurrentDrawer() {
		return currentDrawer;
	}

	/**
	 * Returns the connection stored to access the current drawer
	 * 
	 * @return
	 */
	public synchronized Socket getCurrentDrawerConnection() {
		return clients.get(getCurrentDrawer());
	}

	/**
	 * Returns the connection to a user.
	 * 
	 * @param user
	 * @return null if the user does not exist
	 */
	public synchronized Socket getDrawerConnection(String user) {
		return clients.get(user);
	}

	/**
	 * Returns the time from the last recorded drawer's activity.
	 * 
	 * @return null if and only if no drawer is active
	 */
	public synchronized Date getCurrentDrawerLastActivityDate() {
		return currentDrawerLastActive;
	}

	/**
	 * Returns the total amount of time this drawer has controlled the canvas
	 * 
	 * @return
	 */
	public synchronized Date getCurrentDrawerStartDate() {
		return currentDrawerStartTime;
	}

	/**
	 * Returns the time that must pass before a connection is considered dead
	 * and should be dropped.
	 * 
	 * @return
	 */
	public long getIdleTimeLimit() {
		return idletime;
	}
	
	
	/**
	 * Resets a users idle time
	 */
	public void resetIdleTime(){
		currentDrawerLastActive = new Date();
	}

	/**
	 * Returns about of time each person has to control the canvas
	 * 
	 * @return
	 */
	public long getTimeLimit() {
		return timelimit;
	}


	/**
	 * Sets the current drawing tool
	 * 
	 * @param currentTool
	 */
	public synchronized void setCurrentTool(String currentTool) {
		tool = ServerToolFactory.createTool(currentTool);
		if (tool == null) {
			tool = ServerToolFactory.createTool("freehand");
		}
	}

	/**
	 * Add an action to the canvas history for distribution to new clients or clients
	 * that need updating
	 * @param Action object to store on the canvas
	 */
	public void addCanvasAction(Action a) {
		tool.addAction(a);	
	}

	/**
	 * Add an object to the canvas
	 * @param object
	 */
	public void addObject(DrawingObject object){
		if(object != null){
			canvasObjects.add(object);
		}
	}
	
	/**
	 * Returns a List of Drawings currently on the canvas
	 * @return
	 */
	public synchronized List<DrawingObject> getCanvasObjects(){
		return canvasObjects;
	}
	
	/**
	 * Remove an object from the canvas
	 * @param object
	 */
	public DrawingObject removeObject(){
		if(objectSelected){
			objectSelected=false;
			return canvasObjects.remove(canvasObjects.size()-1);
		}
		return null;
	}
	

	/**
	 * selects an object at point (x,y)
	 * @param x
	 * @param y
	 * @return
	 */
	public DrawingObject selectObject(int x,int y){
		objectSelected=false;
		for(int i=0; i < canvasObjects.size();i++){
			if(canvasObjects.get(i).isPointInObject(x,y)){
				//move to the top
				canvasObjects.add(canvasObjects.remove(i));
				objectSelected=true;
				break;
			}
		}
		//System.out.println("select "+objectSelected);
		if(objectSelected){
			return canvasObjects.get(canvasObjects.size()-1);
		}else{
			return null;
		}
	}
	
	/**
	 * Moves the selected object
	 * 
	 */
	public void moveObject(int x, int y) {
		//System.out.println("move "+objectSelected);
		if(objectSelected){
			canvasObjects.get(canvasObjects.size()-1).move(x,y);
		}
	}
	
	/**
	 * Colors the selected object
	 */
	public void colorSelected() {
		if(objectSelected){
			canvasObjects.get(canvasObjects.size()-1).setColor(getCurrentColor());
		}
	}
	
	/**
	 * Returns the current color set on the canvas
	 * @return
	 */
	public synchronized String getCurrentColor(){
		return currentColor;
	}
	
	/**
	 * Allows you to set the current color for tools on the canvas
	 * @param currentColor
	 */
	public  void setCurrentColor(String currentColor){
		this.currentColor = currentColor;
		tool.setColor(currentColor);
		
		if(this.currentColor == null || "".equals(currentColor)){
			this.currentColor = "black";
			tool.setColor("black");
		}
	}
	/**
	 * Returns a List of Socket connections for all connected clients with the exception
	 * of the current drawer
	 * @return
	 */
	public synchronized List<Socket> getViewingClientSockets() {
		List<Socket> socketList = new ArrayList<Socket>();
		
		for(int i=0; i< viewingClientsList.size(); i++){
			socketList.add(getDrawerConnection(viewingClientsList.get(i)));
		}
		return socketList;
	}
	
	/**
	 * adds param to viewingClientsList list
	 * 
	 * @param user name to be added to list
	 */
	public void addToViewingClients(String user){
		if(!viewingClientsList.contains(user)){
			viewingClientsList.add(user);
		}	
	}
	
	/**
	 * removes param from viewingClientsList list
	 * @param user name to be removed from list
	 */
	public synchronized void removeFromViewingClients(String user){
		if(viewingClientsList.contains(user)){
			viewingClientsList.remove(user);
		}
	}

	/**
	 * removes all objects from the canvasObjects list.
	 * clears the canvas
	 */
	public void removeAllObjects() {
		canvasObjects.clear();
	}

	/**
	 * Returns the number of clients currently connected to the server
	 * @return
	 */
	public synchronized int getTotalClientCount() {
		return clients.size();
	}
}
